local type,tostring,select,pairs,ipairs,assert=type,tostring,select,pairs,ipairs,assert
local string_len,string_rep,string_match,string_format,string_find,string_byte=
	  string.len,string.rep,string.match,string.format,string.find,string.byte
local table_insert,table_remove=
	  table.insert,table.remove
module(..., package.seeall)

require'zdc.logger'
require'zdc.node'
require'zdc.fieldutil'
require'zdc.typeex'
require'zdc.deflate'
local nodeapi=require'zdc.nodeapi'

local dfskey=zdc.fieldutil.dfskey

Dpl = {}
local Dpl_mt = {__metatable={}, __index=Dpl}


function new(dal,db_module, db_params, prototype_id, nodenew, sandboxenv, secugroups,auth,logger,childmt)
	local repo = setmetatable({},childmt or Dpl_mt)
	repo.dal=dal
	repo._pto_id = prototype_id or "@root"
	assert(db_module,'bad db_module: '..tostring(db_module))
	if type(db_module)=='table' then
		repo._db = db_module
	else
		local dbm = require(db_module)
		repo._db = dbm.new(db_params)
	end
	repo._secugroups = secugroups or {}
	repo._auth=auth
	repo._nodenew = nodenew or zdc.node.new
	repo._sb = sandboxenv
	repo._log = logger
	repo._cache = {}
	return repo
end

function BuildDpl(dal,config,glbsbenv)
	local config = config or {}
	local sbenv
	if glbsbenv then
		local csb=config.sbenv
		sbenv=setmetatable({}, {__index = function (n,k) local a=csb[k]; if a~=nil then return a else return glbsbenv[k] end end})
	else
		sbenv=setmetatable({}, {__index = config.sbenv})
	end
	local new=new 
	if config.dplmodule then
		new = require(config.dplmodule).new
		print('use dpl',config.dplmodule,new)
	end
	local dpl = new(dal,config.DB.MODULE,config.DB.PARAMS,config.ROOT_PROTOTYPE or '@root',config.nodenew,sbenv,config.secugroups,config.auth,config.log or zdc.logger.defaultlog)
	dpl._no_reset_cache=config.NO_RESET_CACHE
	dpl._get_fallback_node = config._get_fallback_node
	dpl._nouse_parent_cfd=config._nouse_parent_cfd
	dpl._talist = {}
	dpl._treeid=config.treeid
	return dpl
end

function Dpl:SetSBENV(config_node_id)
	if config_node_id then
		local config_node = self.dal:SureNode(config_node_id)
		assert(config_node,"can't load config node: " .. config_node_id)
		local new_config = config_node.content or {}
		assert(new_config and type(new_config)=="table","Config node's content should evaluate to a table")
		local config = setmetatable(new_config, {__index=self._sb})
		self._sb = setmetatable({}, {__index = config})
		self:_reset_cache()
	end
	return self
end


function Dpl:_drop_all(opt)
	if not self._treeid then
		print('WARN','dpl not has a _treeid, can not be drop all')
		return
	end
	self._db:transdo(function()
		self:_remove_node(self._treeid,true)
	end)
	self:_reset_cache(true)
	return
end

--[[
1: save node
2: remove node
3: remove node with sub
--]]
function Dpl:_ta_inta()
	return #self._talist>0
end
function Dpl:_ta_abort()
	self._talist = {}
end
function Dpl:_ta_save_node(n)
	local lst=self._talist
	local chkid=n.__id
	self._cache[chkid] = n.__org
	if #lst>0 then
		local o=lst[#lst]
		if o[1]==1 and o[2].__id==chkid then
			o[2]=n
		else
			--bad perf, TODO?
			--[[for i=1,#lst-1 do
				local o=lst[i]
				if o[1]==1 and o[2].__id==chkid then
					o[1]=0
				end
			end
			--]]
			table.insert(lst,{1,n})
		end
	else
		table.insert(lst,{1,n})
	end
end
function Dpl:_ta_remove_node(id,deep)
	self:_del_cache(id,deep)
	table.insert(self._talist,{(deep and 3) or 2,id})
end
function Dpl:_ta_remove_subnodes(parentid,deep)
	self:_del_sub_cache(parentid)
	table.insert(self._talist,{4,parentid})
end
function Dpl:_ta_commit()
	local lst=self._talist
	self._talist={}
	local allnum=#lst
	if allnum>0 then
		return (self._db:transdo(function()
			for i=1,allnum do
				local o=lst[i]
				local op=o[1]
				if op==0 then
					--opt empty
				elseif op==1 then
					self:_save_node(o[2])
				elseif op==2 then
					self:_remove_node(o[2])
				elseif op==3 then
					self:_remove_node(o[2],true)
				elseif op==4 then
					self:_remove_subnodes(o[2])
				else
					error('unknow op ' .. op .. ' for Dpl:_ds_commit')
				end
			end
			return lst
		end))
	else
		return lst
	end
end

function Dpl:_reset_cache(force)
	if force or not self._no_reset_cache then
		self._cache = {}
	end
	if force then
		if self._db.reset_cache then
			self._db:reset_cache()
		end
	end
end

function Dpl:_put_cache(node)
	assert(not self._cache[node.__id]) 
	self._cache[node.__id]=node
end

function Dpl:_del_sub_cache(idparent)
	local prefix = idparent .. '/'
	local preflen = #prefix
	for _i, _ in pairs(self._cache) do
		if #_i > preflen and string.sub(_i,1, preflen) == prefix then
			self._cache[_i] = nil
		end
	end
end

function Dpl:_del_cache(id,deep)
	if deep then
		self:_del_sub_cache(id)
	end
	self._cache[id]=nil
end

function Dpl:_has_node(id)
	if self._cache[id] then return true end
	return self._db:node_exists(id)
end

local function checkautocfg(cfd,k)
	for i=1,#cfd do
		if string.match(k,cfd[i]) then return true end
	end
end

function Dpl:_get_node(id)
	if not id then return nil,'nil id' end
	local cache_key = id
	local _sca = self._cache
	if _sca[cache_key] then
		return _sca[cache_key]
	end

	local dbn = self._db:get_node(id)
	if dbn then
		local node,errmsg = self:_make_node(id,dbn.data,dbn.ik,dbn.ts)
		if not node then print(errmsg) end
		_sca[cache_key] = node
		return node,errmsg
	end
	
	if not self._nouse_parent_cfd then
		local parent_id, rest = string.match(id, "^(.+)/(.-)$")
		if parent_id then
			local parent = self.dal:GetNode(parent_id)
			if parent then
				local cfd,cad=parent._cfd,parent._cad
				if (cfd and cfd[rest]) or (cad and checkautocfg(cad,rest))then
					local node,errmsg = parent:__make_child(rest,mc)
					if node then
						_sca[cache_key] = node
						return node,errmsg
					end
					if errmsg then print(errmsg) end
				end
			end
		end
	end
	if self._get_fallback_node then
		local prototype = nil
		local node, stub = self:_get_fallback_node(id)
		if not stub then
			_sca[cache_key] = node
		end
		return node,stub
	end
end

function Dpl:_deflate_node(node,fields,dyfields)
	return self._db:deflate_node(node,fields,dyfields)
end

function Dpl:_sure_node(id,mc,nocache)
	local cache_key = id
	local _sca = self._cache
	if _sca[cache_key] then
		return _sca[cache_key]
	end

	local dbn = self._db:get_node(id)
	if dbn then
		local node,errmsg = self:_make_node(id,dbn.data,dbn.ik,dbn.ts)
		if not node then print(errmsg) end
		if not nocache then _sca[cache_key] = node end
		return node,errmsg
	end

	local parent_id, rest = string.match(id, "^(.+)/(.-)$")
	if parent_id then
		local parent = self.dal:SureNode(parent_id)
		if parent then
			local node,errmsg = parent:__make_child(rest,mc)
			if node then
				if not nocache then _sca[cache_key] = node end
				return node,errmsg
			end
			if errmsg then print(errmsg) end
		end
	end

	if self._get_fallback_node then
		local prototype = nil
		local node, stub = self:_get_fallback_node(id)
		if not (nocache or stub)then _sca[cache_key] = node end
		return node
	end
end

function Dpl:_make_node(id,data,ik,ts,firstcreate,preload)
	return self._nodenew{dpl=self,data=data, id=id, repository=self, ik=ik, ts=ts,firstcreate = firstcreate,preload = preload}
end

function Dpl:_save_node(node)
	assert(node.__id)
	local norg=node.__org
	self._cache[norg.__id] =norg
	local ts=norg.__ts_ or os.time()
	self._db:save_node(norg.__id,self._db:deflate_node(norg.__raw_v, norg._fds,norg._dfs),rawget(norg,'__ik'),ts)
	rawset(norg,'__ts_',nil)
	norg.__ts=ts
	norg.__needsave=false
	rawset(norg,'__is_a_stub',nil)
end

function Dpl:_save_data(id,nodedata)
	assert(id)
	self._cache[id] = nil
	self._db:save_node(id,nodedata.data,nodedata.ik,nodedata.ts)
end

function Dpl:_call_node(user,id,act,...)
	local n=self:_get_node(id)
	if not n then return false,'EN1NE:'..tostring(id) end
	return nodeapi.callact(user,self.dal,n,act,...)
end
function Dpl:_acall_node(user,id,cb,act,...)
	local n=self:_get_node(id)
	if not n then return false,'EN1NE:'..tostring(id) end
	return nodeapi.asynccall(user,self.dal,cb,n,act,...)
end
function Dpl:_get_child_nodes(parentid,cn)
	local _sca = self._cache
	local t=self._db:get_child_nodes(parentid)
	local node
	cn=cn or {}
	local lp=#parentid+2
	for id,dbn in pairs(t) do
		local sid=string.sub(id,lp)
		if not cn[sid] then
			if _sca[id] then
				node=_sca[id]
			else
				node= self:_make_node(id,dbn.data,dbn.ik,dbn.ts)
				_sca[id] = node
			end
			cn[sid]=node
		else
			self._log:error('dup node ',id)
		end
	end
	return cn
end

function Dpl:_get_child_nodes_ex(parentid,cn,where,sort,limit)
	local _sca = self._cache
	local t,lst=self._db:get_child_nodes_ex(parentid,where,sort,limit)
	local node
	cn=cn or {}
	local lp=#parentid+2
	for i=1,#lst do
		local id=lst[i]
		local dbn=t[id]
		local sid=string.sub(id,lp)
		lst[i]=sid
		if not cn[sid] then
			if _sca[id] then
				node=_sca[id]
			else
				node= self:_make_node(id,dbn.data,dbn.ik,dbn.ts)
				_sca[id] = node
			end
			cn[sid]=node
		else
			self._log:error('dup node ',id)
		end
	end
	return cn,lst
end

function Dpl:_get_child_rawfields_ex(parentid,fields,where,sort,limit)
	local _db=self._db
	if _db.get_child_rawfields_ex then
		return _db:get_child_rawfields_ex(parentid,fields,where,sort,limit)
	end
	local all,lst=self:_get_child_nodes_ex(parentid,nil,where,sort,limit)
	local raws={}
	if type(fields)=='string' then
		local seg={}
		for s in string.gmatch(id,'[^,;]+') do
			table.insert(seg,s)
		end
		for i,k in ipairs(lst) do
			local v=all[k]
			local obj={}
			for ii=1,#seg do
				local kk=seg[ii]
				obj[kk]=v[kk]
			end
			obj.__sid=k
			table.insert(raws,obj)
		end
		fields=seg
	end
	if type(fields)=='table' then
		for i,k in ipairs(lst) do
			local v=all[k]
			local obj={}
			for ii=1,#fields do
				local kk=fields[ii]
				obj[kk]=v[kk]
			end
			obj.__sid=k
			table.insert(raws,obj)
		end
	else
		for i,k in ipairs(lst) do
			local v=all[k]
			local obj={}
			for kk,vv in pairs(v.__raw_v) do
				if string_byte(kk)~=95 then
					obj[kk]=vv
				end
			end
			obj.__sid=k
			table.insert(raws,obj)
		end
	end
	return raws
end

function Dpl:_get_prefix_nodes_ex(prefix,cn,where,sort,limit)
	local _sca = self._cache
	local t,lst=self._db:get_nodes_by_prefix_ex(prefix,where,sort,limit)
	local node
	cn=cn or {}
	for id,dbn in pairs(t) do
		if not cn[id] then
			if _sca[id] then
				node=_sca[id]
			else
				node= self:_make_node(id,dbn.data,dbn.ik,dbn.ts)
				_sca[id] = node
			end
			cn[id]=node
		else
			self._log:error('dup node ',id)
		end
	end
	return cn,lst
end

function Dpl:_get_child_ids_ex(parentid,where,sort,limit)
	return self._db:get_child_ids_ex(parentid,where,sort,limit)
end

function Dpl:_get_prefix_ids_ex(prefix,where,sort,limit)
	return self._db:get_ids_by_prefix_ex(prefix,where,sort,limit)
end

function Dpl:_get_child_cnt_ex(parentid,where)
	return self._db:get_child_cnt_ex(parentid,where)
end

function Dpl:_get_prefix_cnt_ex(prefix,where)
	return self._db:get_cnt_by_prefix_ex(prefix,where)
end

function Dpl:_remove_node(id,deep)
	self:_del_cache(id,deep)
	self._db:del_node(id, deep)
end

function Dpl:_remove_subnodes(idparent)
	self:_del_sub_cache(idparent)
	self._db:del_sub_nodes(idparent)
end
